﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		TextState.cs
//
//	Author:		MJP
//
//	Date:		04/19/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Drawing.Design;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CPXMenu
{
    /// <summary>
    /// Used to configure an draw an image for different MenuItem's
    /// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class TextState : IContentOwner
    {
        private Point dimensions;
        private ContentManager contentManager = null;
        private Color fontColor = Color.White;
        private Color backgroundColor = Color.TransparentWhite;
        private Texture2D whiteTexture = null;
        private SpriteFont font = null;
		private string fontName = "CPXMenu Content\\Fonts\\ArialM";
        private string text = "";
        private bool wordWrap = true;
        private List<string> lines = new List<string>();
        private TextHorizontalJustification horizontalJustification = TextHorizontalJustification.Left;
        private TextVerticalJustification verticalJustification = TextVerticalJustification.Top;   
        private float transitionTime = 0;
        private MenuItem owner = null;

        /// <summary>
        /// Gets or sets the name of the SpriteFont to load out of
        /// the ContentManager, and use for font rendering.  Setting
        /// to null or an empty string will set the SpriteFont to null,
        /// and no text will be rendered.
        /// </summary>
        [Browsable(true)]
        [Description("The name of the SpriteFont to use")]
        [Editor(typeof(ContentPickerEditor<SpriteFont>), typeof(UITypeEditor))]
        public string FontName
        {
            get { return fontName; }
            set
            {
                fontName = value;
                if (!string.IsNullOrEmpty(fontName))
                {
                    if (contentManager != null)
                        font = contentManager.Load<SpriteFont>(fontName);
                    else
                        font = null;
                }
                else
                    font = null;
            }
        }

        /// <summary>
        /// Gets or sets the text to be rendered
        /// </summary>
        [Browsable(true)]
        [Description("The text to draw")]
        public string Text
        {
            get { return text; }
            set 
            { 
                text = value;
                GenerateLines();
            }
        }

        /// <summary>
        /// Gets or sets the background color of the text
        /// </summary>
        [Browsable(true)]
        [Description("Controls the color drawn behind the text")]
        [Editor(typeof(ColorPickerEditor), typeof(UITypeEditor))]
        public Color BackgroundColor
        {
            get { return backgroundColor; }
            set { backgroundColor = value; }
        }

        /// <summary>
        /// Gets or sets the color of the text that is drawn
        /// </summary>
        [Browsable(true)]
        [Description("Controls the color of the text that is drawn")]
        [Editor(typeof(ColorPickerEditor), typeof(UITypeEditor))]
        public Color FontColor
        {
            get { return fontColor; }
            set { fontColor = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the text should be wrapped to
        /// fit on the Label.
        /// </summary>
        [Browsable(true)]
        [Description("If true, wraps the text to fit on the width of the Label")]
        public bool WordWrap
        {
            get { return wordWrap; }
            set 
            { 
                wordWrap = value;
                GenerateLines();
            }
        }

        [Browsable(true)]
        [Description("The horizontal justification of the text")]
        public TextHorizontalJustification HorizontalJustification
        {
            get { return horizontalJustification; }
            set { horizontalJustification = value; }
        }

        [Description("The vertical justification of the text")]
        public TextVerticalJustification VerticalJustification
        {
            get { return verticalJustification; }
            set { verticalJustification = value; }
        }

        /// <summary>
        /// Get or Sets the dimensions of the TextState bounding box
        /// </summary>
        [Browsable(false)]
        internal Point Dimensions
        {
            get { return dimensions; }
            set 
            {
                dimensions = value;
                GenerateLines();
            }
        }

        /// <summary>
        /// Gets the ContentManager
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ContentManager Content
        {
            get { return contentManager; }
        }

        /// <summary>
        /// Gets or sets a value indicating the time it takes to transition to this state
        /// </summary>
        [Browsable(true)]
        [Description("The time it takes to transition to this state")]
        public float TransitionTime
        {
            get { return transitionTime; }
            set { transitionTime = MathHelper.Max(0, value); }
        }

        /// <summary>
        /// Gets the item that owns the content
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public MenuItem OwnerItem
        {
            get { return owner; }
        }

        /// <summary>
        /// Initializes the TextState
        /// </summary>
        /// <param name="contentManager">The ContentManager to use for loading resources</param>
        internal void Initialize(ContentManager contentManager, Point dimensions, MenuItem ownerItem)
        {
            this.contentManager = contentManager;
            this.owner = ownerItem;

			whiteTexture = contentManager.Load<Texture2D>("CPXMenu Content\\Textures\\BlankDiffuse");

            FontName = fontName;
            Dimensions = dimensions;
        }

        /// <summary>
        /// Generates the lines for word wrapping
        /// </summary>
        private void GenerateLines()
        {
            lines.Clear();

            if (string.IsNullOrEmpty(text))
                return;

            if (font == null)
                return;

            if (wordWrap)
            {
                string[] words = text.Split(" ".ToCharArray());
                float left = 0;
                string currentLine = "";
                int lineWords = 0;

                for (int i = 0; i < words.Length; i++)
                {
                    Vector2 size = font.MeasureString(words[i]);
                    left += size.X;
                    if (left >= Dimensions.X)
                    {
                        if (lineWords == 0)
                            currentLine = words[i];

                        lines.Add(currentLine);

                        if (lineWords != 0)
                        {
                            currentLine = words[i];
                            left = size.X;
                            lineWords = 1;
                        }
                        else
                        {
                            currentLine = "";
                            left = 0;
                            lineWords = 0;
                        }
                    }
                    else
                    {
                        if (lineWords > 0)
                            currentLine += " ";

                        currentLine += words[i];
                        lineWords++;
                    }
                }

                if (currentLine != "")
                    lines.Add(currentLine);
            }
            else
                lines.Add(text);
        }

        /// <summary>
        /// Creates a duplicate TextState
        /// </summary>
        /// <returns>The duplicate TextState</returns>
        internal TextState Clone()
        {
            return (TextState)this.MemberwiseClone();
        }

        /// <summary>
        /// Called when owned imported content is removed from the project
        /// </summary>
        /// <param name="contentItem">The item that was removed</param>
        internal void OnOwnedContentRemoved(ImportedContentItem contentItem)
        {
            if (fontName == contentItem.Path)
                fontName = string.Empty;
        }

        /// <summary>
        /// Creates a string representation of the TextState
        /// </summary>
        /// <returns>The string representation of the TextState</returns>
        public override string ToString()
        {
            return this.GetType().Name;
        }

        /// <summary>
        /// Renders the image
        /// </summary>
        /// <param name="spriteBatch">The SpriteBatch to use for drawing</param>
        /// <param name="drawRect">The rectangle (absolute coordinates) to which the text should be drawn</param>
        public void Render(SpriteBatch spriteBatch, Rectangle drawRect, float alpha)
        {
            alpha = MathHelper.Clamp(alpha, 0, 1);

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);

            Color bgColor = backgroundColor;
            bgColor.A = (byte)MathHelper.Lerp(0, backgroundColor.A, alpha);
            spriteBatch.Draw(whiteTexture, drawRect, bgColor);

            if (font != null && !string.IsNullOrEmpty(text))
            {
                Color textColor = fontColor;
                textColor.A = (byte)MathHelper.Lerp(0, fontColor.A, alpha);

                Vector2 drawPos = new Vector2(drawRect.X, drawRect.Y);
                float lineHeight = font.MeasureString(text).Y;
                float totalHeight =  lineHeight * lines.Count;

                if (verticalJustification == TextVerticalJustification.Bottom)
                    drawPos.Y += dimensions.Y - totalHeight;
                else if (verticalJustification == TextVerticalJustification.Center)
                {
                    float centerY = dimensions.Y / 2.0f;
                    drawPos.Y += centerY - (totalHeight / 2.0f);
                }

                for (int i = 0; i < lines.Count; i++)
                {
                    float lineWidth = font.MeasureString(lines[i]).X;
                    if (horizontalJustification == TextHorizontalJustification.Right)
                        drawPos.X = drawRect.X + drawRect.Width - lineWidth;
                    else if (horizontalJustification == TextHorizontalJustification.Center)
                    {
                        float centerX = dimensions.X / 2.0f;
                        drawPos.X = drawRect.X + centerX - (lineWidth / 2.0f);
                    }

                    spriteBatch.DrawString(font, lines[i], drawPos, textColor);
                    drawPos.Y += lineHeight;
                }
            }

            spriteBatch.End();
        }
    }

    public enum TextHorizontalJustification
    {
        Left = 0,
        Right = 1,
        Center = 2
    }

    public enum TextVerticalJustification
    {
        Top = 0,
        Bottom = 1,
        Center = 2
    }
}